#include "precompiled.hpp"
#include "rules.hpp"
#include "exec_context.hpp"
#include "detail/ScopeGuard.h"

jam_rule::jam_rule(jam_context *context, jam_module *module,
	const jam_string &name) : context_(context), module_(module),
	name_(name)
{
}

void jam_rule::add_optional_param(const jam_string &name)
{
	param_desc &sub=add_list_param();		
	sub.sublist_.push_back(name);
	sub.num_sub_opt_=1;		
}

void jam_rule::add_param(param_type tp, const jam_string &name)
{
	params_.push_back();
	ScopeGuard guard=MakeObjGuard(params_,&param_list::pop_back);

	param_desc &desc=params_.back();
	desc.name_=name;
	desc.type_=tp;

	guard.Dismiss();
}

//TODO: add name validation
const jam_string& jam_rule::check_name(const jam_string& name)
{
	return name;
}

jam_rule::param_desc& jam_rule::add_list_param()
{
	params_.push_back();
	ScopeGuard guard=MakeObjGuard(params_,&param_list::pop_back);

	param_desc &desc=params_.back();
	desc.name_=jam_string(boost::cref("(list)"),6);;
	desc.type_=sublist;
	
	guard.Dismiss();
	return desc;
}

void jam_rule::invoke(jam_frame *frame, jam_list *res)
{
	//Check arguments
	const list_of_lists &args=frame->args_;
	if (args.size()>params_.size())
		argument_error("extra argument(s)",frame);
	for(size_t f=0,fend=args.size();f!=fend;f++)
	{
		assert(params_[f].type_!=none);
		switch(params_[f].type_)
		{
		case vararg:
		break;
		case plain:
			if (!args[f].size_is_one())
				argument_error("wrong argument(s)",frame);
		break;		
		case one_or_more:
			if (args[f].empty())
				argument_error("missing argument(s)",frame);
		break;
		case sublist:
			{
				const param_desc &desc=params_[f];
				size_t actual_num=args[f].size(), formal_num=desc.sublist_.size(),
					formal_opt_num=desc.num_sub_opt_;
				if ( (actual_num>formal_num && !desc.last_subparam_vararg_) 
					|| actual_num<(formal_num-formal_opt_num))					
					argument_error("mismatched argument(s)",frame);
			}
		break;
		}
	}

	do_invoke(frame,res);
}

void jam_rule::print_arguments(std::ostream &str)
{
	for(size_t f=0;f<params_.size();f++)
	{
		if (f!=0) str<<" ";

		assert(params_[f].type_!=none);
		switch(params_[f].type_)
		{
		case plain:
			str<<params_[f].name_;
			break;
		case one_or_more:
			str<<params_[f].name_<<" +";
			break;
		case vararg:
			str<<params_[f].name_<<" *";
			break;
		case sublist:
			str<<params_[f].sublist_;
			if (params_[f].last_subparam_vararg_)
				str<<" *";
			break;
		}
		if (f!=(params_.size()-1)) str<<" :";
	}
}

void jam_rule::argument_error(const char* message, jam_frame *frame)
{	
	assert(frame->node_);
	context_->backtrace_line(frame->prev_);
	context_->err()<<"*** argument error"<<std::endl;
	context_->err()<<"* rule "<<frame->scope_name_<<" ( ";
	print_arguments(context_->err());
	context_->err()<<" )"<<std::endl;
	context_->err()<<"* called with: ( ";
	context_->err()<<frame->args_;
	context_->err()<<" )"<<std::endl;
	context_->err()<<"* "<<message<<std::endl;
	context_->err()<<get_file()<<":"<<get_line()<<": ";
	context_->err()<<"see definition of rule '"<<get_name()<<"' being called"<<std::endl;
	context_->backtrace(frame->prev_);
	throw jam_exception("Interpreter exception");
}
